package ks.client.ipc;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import ks.client.processor.ClientProcessor;
import ks.framework.common.Message;
import ks.framework.interfaces.IClientProcessor;

/**
 * Client inter-process communication component.
 * <p>
 * Responsible for initiating a connection to the server inter-process
 * communication component.  This manages both the Talker to the server
 * and the reader from the server.
 * <p>
 * Provides an abstract to client-side code that need to contact the 
 * server. To connect, call the {@link #connect(String, int, String, String, boolean)}
 * method; to disconnect, call {@link #disconnect()}. During the connect
 * invocation, the plaintext password is hashed using {@link #sha1(String)}
 * logic, according to the standard SHA-1 encryption algorithm. The server
 * will be responsible for matching passwords accordingly using only the
 * hashed value. To send a message to the server, simply call 
 * {@link #sendToServer(Message)} with the desired message and it will be
 * delivered.
 * <p>
 * When messages are received from the server, the {@link #process(Message)}
 * delegates the processing of these messages to the {@link #processor} entity
 * that was set via the {@link #setProcessor(IClientProcessor)} invocation.
 * This client entity then acts as a true middle-man.
 * 
 * @author George Heineman
 */
public class Client  {
	/** Talker to communicate via network. */
	Talker talker;
	
	/** Reader to process returning Messages. */
	Reader reader;
	
	/** Determines if client is connected. */
	boolean isConnected = false;
	
	/** Each client knows the ClientProcessor to whom requests are sent. */
	ClientProcessor processor;
	
	/**
	 * Method for converting a number from byte to hexadecimal.
	 * <p>
	 * @param b	byte to be converted to hex
	 * @return	String representing hex conversion of give byte
	 */
	private static String byteToHex(byte b){
		int holder =(int) b;
		String x = Integer.toHexString(holder);
		holder = (Integer.SIZE/4) - (Byte.SIZE/4);
		while(x.length() < 8){
			x = "0" + x;
		}
		return x.substring(holder);
	}
	
	/**
	 * Provided SHA-1 encryption for plain-text passwords.
	 * 
	 * @param text
	 * @return
	 */
	public static String sha1(String text){
		byte[] toDigest = text.getBytes();
		MessageDigest sha;
		try {
			sha = MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return text;
		}
		sha.update(toDigest);
		toDigest = sha.digest();
		String rString = "";
		for (int i = 0; i < toDigest.length; i++){
			rString = rString + byteToHex(toDigest[i]);
		}
		return rString;
	}

	/** 
	 * Initiate the connection to the server.
	 * 
	 * If selfRegister is set to true then the user/password values are ignored
	 * and the server simply creates a new account for the user.
	 * 
	 * @param hostname    desired target host
	 * @param port        desired port
	 * @param user        user wishing to connect
	 * @param password    hashed password
	 */
	public void connect(String hostname, int port, String user, String password, boolean selfRegister) {
		talker = new Talker (this);
			
		talker.setvalues(hostname, port, user, password, selfRegister);
		talker.start();
	}

	/** Disconnect from the server. */
	public boolean disconnect() {
		if (talker != null) {
			talker.disconnect();
			talker = null;
			return true;
		}
		
		return false;
	}
	
	/**
	 * Deliver message to the server using {@link #talker} thread.
	 * 
	 * @param m
	 */
	public boolean sendToServer(Message m) {
		if (!isConnected) return false;
		
		return talker.writeObject(m);
	}
	
	/** 
	 * This method is invoked by the Talker once a connection has been
	 * established (or terminated).
	 * 
	 * @param status
	 */
	public void connected(boolean status) {
		
		if (status) {
			reader = new Reader (talker.getAgent(), this);
			reader.start();
		} else {
			reader = null;
		}
		
		// pass along to processor
		isConnected = status;
		processor.connected(status);
	}

	/**
	 * This message has been received from the server. It must be
	 * delegated to the appropriate processor on the client side.
	 * 
	 * @param m
	 */
	public void process(Message m) {
		processor.process(m);
	}

	/**
	 * Set the processor to respond to client messages from server.
	 * 
	 * @param p
	 */
	public void setProcessor(ClientProcessor p) {
		this.processor = p;
	}

}
